import { writeFile } from "fs";
import path from "path";
import { Address4, Address6 } from "ip-address";
import fetch from "node-fetch";
import xml2js from "xml2js";
import dayjs from "dayjs";

const TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxifierProfile version="102" platform="Windows" product_id="0" product_minver="400">
	<Options>
		<Resolve>
			<AutoModeDetection enabled="true" />
			<ViaProxy enabled="false" />
			<ExclusionList OnlyFromListMode="false">%ComputerName%; localhost; *.local</ExclusionList>
			<DnsUdpMode>0</DnsUdpMode>
		</Resolve>
		<Encryption mode="basic" />
		<ConnectionLoopDetection enabled="true" resolve="true" />
		<ProcessOtherUsers enabled="false" />
		<ProcessServices enabled="false" />
		<HandleDirectConnections enabled="false" />
		<HttpProxiesSupport enabled="false" />
	</Options>
	<ProxyList>
		<Proxy id="100" type="SOCKS5">
			<Options>48</Options>
			<Port>10808</Port>
			<Address>127.0.0.1</Address>
		</Proxy>
	</ProxyList>
	<ChainList />
	<RuleList>
		<Rule enabled="true">
			<Action type="Direct" />
			<Applications>xray.exe; wv2ray.exe; v2rayupgrade.exe; v2rayn.exe; v2ray.exe; v2ctl.exe</Applications>
			<Name>v2rayN</Name>
		</Rule>
		<Rule enabled="true">
			<Action type="Direct" />
			<Targets>*.test-ipv6.*;*.ipv6-test.*;*.testipv6.*;*.testipv6.*;api-ipv4.ip.sb;api-ipv6.ip.sb;ipv4.ddnspod.com;ipv6.ddnspod.com;ipv4.icanhazip.com;ipv6.icanhazip.com;v4.ipv6-test.com;v6.ipv6-test.com;ipv4.testipv6.cn;ipv6.testipv6.cn;*.ip138.com;*.whatismyip.com;*.ip.me;*.whatismyipaddress.com;*.ipify.org;*.seeip.org;</Targets>
			<Name>getip</Name>
		</Rule>
		<Rule enabled="true">
			<Action type="Proxy">100</Action>
			<Targets>*.docker.com</Targets>
			<Name>proxy</Name>
		</Rule>
		<Rule enabled="true">
			<Action type="Proxy">100</Action>
			<Name>Default</Name>
		</Rule>
	</RuleList>
</ProxifierProfile>
`;

type RuleType = {
  $: {
    enabled: "true" | "false";
  };
  Action: {
    $: {
      type: "Direct";
    };
  }[];
  Targets: string[];
  Name: string[];
};

const IPV4_LOCAL_CIDRS = [
  "0.0.0.0/8",
  "10.0.0.0/8",
  "100.64.0.0/10",
  "127.0.0.0/8",
  "169.254.0.0/16",
  "172.16.0.0/12",
  "192.0.0.0/24",
  "192.0.2.0/24",
  "192.88.99.0/24",
  "192.168.0.0/16",
  "198.18.0.0/15",
  "198.51.100.0/24",
  "203.0.113.0/24",
  "224.0.0.0/4",
  "240.0.0.0/4",
  "255.255.255.255/32",
];

const IPV6_LOCAL_CIDRS = ["::1/128", "fc00::/7", "fe80::/10"];

/**
 * 获取 中国 IP CIDR 数组
 * @param type ip类型
 * @returns ip CIDR 数组
 */
async function getIPAddresss(
  type: "ipv4" | "ipv6" = "ipv4"
): Promise<string[]> {
  let url = "https://gaoyifan.github.io/china-operator-ip/china.txt";
  if (type === "ipv6") {
    url = "https://gaoyifan.github.io/china-operator-ip/china6.txt";
  }
  try {
    const res = await fetch(url);
    if (res.ok) {
      const resBody = await res.text();
      const list = resBody.split("\n");
      return list.filter((v) => v.trim() !== "");
    }
    return [];
  } catch (err) {
    console.error(err);
    return [];
  }
}

/**
 * 将 CIDR 地址转换为 IP地址范围 startIP-endIP
 * @param cidr CIDR格式地址
 * @returns
 */
function convertCIDRToRange(cidr: string): string {
  let ip: Address4 | Address6 | null = null;
  try {
    ip = new Address4(cidr);
  } catch {}
  if (!ip) {
    try {
      ip = new Address6(cidr);
    } catch {}
  }
  if (ip) {
    return `${ip.startAddress().address}-${ip.endAddress().address}`;
  }
  return "";
}

/**
 * 将 CIDR格式地址数组 转换为 IP地址范围数组 ['startIP-endIP']
 * @param cidrs CIDR格式地址数组
 * @returns 地址范围数组
 */
function convertCIDRsToRanges(cidrs: string[]): string[] {
  let list: string[] = [];

  for (let cidr of cidrs) {
    const range = convertCIDRToRange(cidr);
    if (range) {
      list.push(range);
    }
  }
  return list;
}

/**
 * 根据模板合并规则
 * @param rules 规则
 */
async function mergeTemplate(rules: RuleType[]): Promise<string> {
  try {
    let template = await xml2js.parseStringPromise(TEMPLATE);
    template.ProxifierProfile.RuleList[0].Rule.splice(-1, 0, ...rules);
    const builder = new xml2js.Builder();
    const xml = builder.buildObject(template);
    return xml;
  } catch (err) {
    console.error(err);
    return "";
  }
}

/**
 * 根据 ip地址范围数组 生产 规则数组
 * @param ipRanges ip地址范围数组
 * @param ruleNamePrefix 规则前缀
 * @param limit 多少 ip地址范围为一组规则
 * @returns 规则数组
 */
function rangesToRules(
  ipRanges: string[],
  ruleNamePrefix: string,
  limit: number = 256
): RuleType[] {
  let rules: RuleType[] = [];
  let count = 0;
  let target = "";
  for (let ipRange of ipRanges) {
    count++;
    target += `${ipRange}; `;
    if (count === limit) {
      const rule: RuleType = {
        $: {
          enabled: "true",
        },
        Action: [
          {
            $: {
              type: "Direct",
            },
          },
        ],
        Targets: [target],
        Name: [`${ruleNamePrefix}-${rules.length + 1}`],
      };
      rules.push(rule);

      // 置空
      count = 0;
      target = "";
    }
  }
  if (target) {
    const rule: RuleType = {
      $: {
        enabled: "true",
      },
      Action: [
        {
          $: {
            type: "Direct",
          },
        },
      ],
      Targets: [target],
      Name: [`${ruleNamePrefix}-${rules.length + 1}`],
    };
    rules.push(rule);
  }
  return rules;
}

async function getIPRanges(type: "ipv4" | "ipv6" = "ipv4"): Promise<string[]> {


  let url =
    "https://cdn.jsdelivr.net/npm/@ip-location-db/dbip-geo-whois-asn-country/dbip-geo-whois-asn-country-ipv4.csv";
  if (type === "ipv6") {
    url =
      "https://cdn.jsdelivr.net/npm/@ip-location-db/dbip-geo-whois-asn-country/dbip-geo-whois-asn-country-ipv6.csv";
  }
  try {
    const res = await fetch(url);
    if (res.ok) {
      const resBody = await res.text();
      const list = resBody.split("\n");
      const cnIps = list.filter((v) => v.includes("CN"));
      const cnRanges = cnIps.map((v) => {
        const temp = v.split(",");
        if (temp.length === 3) {
          let ip1, ip2;
          if (type === "ipv4") {
            ip1 = new Address4(temp[0]);
            ip2 = new Address4(temp[1]);
            return `${ip1.correctForm()}-${ip2.correctForm()}`;
          } else {
            ip1 = new Address6(temp[0]);
            ip2 = new Address6(temp[1]);
            return `${ip1.canonicalForm()}-${ip2.canonicalForm()}`;
          }
        } else {
          return "";
        }
      });
      return cnRanges.filter((v) => v.trim() !== "");
    }
    return [];
  } catch (err) {
    console.error(err);
    return [];
  }
}

async function main() {
  // local
  const ipv4LocalRanges = convertCIDRsToRanges(IPV4_LOCAL_CIDRS);
  console.log("ipv4LocalRanges.length: ", ipv4LocalRanges.length);

  const ipv6LocalRanges = convertCIDRsToRanges(IPV6_LOCAL_CIDRS);
  console.log("ipv6LocalRanges.length: ", ipv6LocalRanges.length);

  // IPV4
  // const ipv4s = await getIPAddresss('ipv4');
  // console.log('ipv4s.length: ', ipv4s.length);
  // const ipv4Ranges = convertCIDRsToRanges(ipv4s);
  const ipv4Ranges = await getIPRanges("ipv4");
  console.log("ipv4Ranges.length: ", ipv4Ranges.length);

  // IPV6
  // const ipv6s = await getIPAddresss('ipv6');
  // console.log('ipv6s.length: ', ipv6s.length);
  // const ipv6Ranges = convertCIDRsToRanges(ipv6s);
  const ipv6Ranges = await getIPRanges("ipv6");

  console.log("ipv6Ranges.length: ", ipv6Ranges.length);
  if (ipv4Ranges.length < 0 || ipv6Ranges.length < 0) {
    console.error("获取IP地址失败");
    return;
  }
  const ipv6AllRange = convertCIDRToRange("::/0");
  const ipv6AllRules = rangesToRules([ipv6AllRange], "all-ipv6");

  const ipv4PrivateRules = rangesToRules(ipv4LocalRanges, "local-ipv4");
  const ipv6PrivateRules = rangesToRules(ipv6LocalRanges, "local-ipv6");
  const ipv4Rules = rangesToRules(ipv4Ranges, "china-ipv4");
  const ipv6Rules = rangesToRules(ipv6Ranges, "china-ipv6");

  [
    {
      name: `绕过大陆IPV4-${dayjs().format("YYYY-MM-DD")}.ppx`,
      rules: [...ipv4PrivateRules, ...ipv6AllRules, ...ipv4Rules],
    },
    {
      name: `绕过大陆IPV4-IPV6-${dayjs().format("YYYY-MM-DD")}.ppx`,
      rules: [
        ...ipv4PrivateRules,
        ...ipv6PrivateRules,
        ...ipv4Rules,
        ...ipv6Rules,
      ],
    },
  ].forEach(async ({ name, rules }) => {
    const newTemplate = await mergeTemplate(rules);
    if (!newTemplate) {
      return;
    }

    const fileName = path.resolve(__dirname, name);
    writeFile(fileName, newTemplate, { encoding: "utf8" }, () => {
      console.log("规则文件：", fileName);
    });
  });
}
main();
